package asteroids.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.antlr.runtime.RecognitionException;

import asteroids.CollisionListener;
import asteroids.IFacade;
import asteroids.model.Asteroid;
import asteroids.model.Bullet;
import asteroids.model.Ship;
import asteroids.model.World;
import asteroids.model.programs.Program;
import asteroids.model.programs.ProgramFactory;
import asteroids.model.programs.TypeChecker;
import asteroids.model.programs.expressions.Expression;
import asteroids.model.programs.parsing.ProgramParser;
import asteroids.model.programs.statements.Statement;
import asteroids.model.programs.types.Type;

public class Facade implements IFacade<World, Ship, Asteroid, Bullet, Program> {

	@Override
	public World createWorld(double width, double height) {
		World world = new World(width, height);
		return world;
	}

	@Override
	public double getWorldWidth(World world) {
		double width = world.getWidth();
		return width;
	}

	@Override
	public double getWorldHeight(World world) {
		double height = world.getHeight();
		return height;
	}

	@Override
	public Set<Ship> getShips(World world) {
		Set<Ship> ships = world.getShips();
		return ships;
	}

	@Override
	public Set<Asteroid> getAsteroids(World world) {
		Set<Asteroid> asteroids = world.getAsteroids();
		return asteroids;
	}

	@Override
	public Set<Bullet> getBullets(World world) {
		Set<Bullet> bullets = world.getBullets();
		return bullets;
	}

	@Override
	public void addShip(World world, Ship ship) {
		world.addShip(ship);
		ship.setWorld(world);
	}

	@Override
	public void addAsteroid(World world, Asteroid asteroid) {
		world.addAsteroid(asteroid);
		asteroid.setWorld(world);
	}

	@Override
	public void removeShip(World world, Ship ship) {
		world.removeShip(ship);
		ship.setWorld(null);
	}

	@Override
	public void removeAsteroid(World world, Asteroid asteroid) {
		world.removeAsteroid(asteroid);
		asteroid.setWorld(null);
	}

	@Override
	public void evolve(World world, double dt, CollisionListener collisionListener) {
		world.evolve(dt, collisionListener);
	}

	@Override
	public Ship createShip(double x, double y, double xVelocity, double yVelocity, double radius, double direction, double mass) {
		return new Ship(radius, mass, new Vector(xVelocity, yVelocity), new Vector(x, y), null, direction);
	}

	@Override
	public boolean isShip(Object o) {
		return o instanceof asteroids.model.Ship;
	}

	@Override
	public double getShipX(Ship ship) {
		return ship.getPositionX();
	}

	@Override
	public double getShipY(Ship ship) {
		return ship.getPositionY();
	}

	@Override
	public double getShipXVelocity(Ship ship) {
		return ship.getVelocityX();
	}

	@Override
	public double getShipYVelocity(Ship ship) {
		return ship.getVelocityY();
	}

	@Override
	public double getShipRadius(Ship ship) {
		return ship.getRadius();
	}

	@Override
	public double getShipDirection(Ship ship) {
		return ship.getDirection();
	}

	@Override
	public double getShipMass(Ship ship) {
		return ship.getMass();
	}

	@Override
	public World getShipWorld(Ship ship) {
		return ship.getWorld();
	}

	@Override
	public boolean isShipThrusterActive(Ship ship) {
		return ship.isThrusting();
	}

	@Override
	public void setThrusterActive(Ship ship, boolean active) {
		ship.setThrusterActive(active);
	}

	@Override
	public void turn(Ship ship, double angle) {
		ship.rotate(angle);
	}

	@Override
	public void fireBullet(Ship ship) {
		ship.fireBullet();
	}

	@Override
	public Asteroid createAsteroid(double x, double y, double xVelocity, double yVelocity, double radius) {
		return new Asteroid(radius, new Vector(xVelocity, yVelocity), new Vector(x, y), null);
	}

	@Override
	public Asteroid createAsteroid(double x, double y, double xVelocity, double yVelocity, double radius, Random random) {
		double angle = (double) Math.toRadians(random.nextInt(360));
		return new Asteroid(radius, new Vector(xVelocity * Math.cos(angle), yVelocity * Math.sin(angle)), new Vector(x, y), null);
	}

	@Override
	public boolean isAsteroid(Object o) {
		return o instanceof asteroids.model.Asteroid;
	}

	@Override
	public double getAsteroidX(Asteroid asteroid) {
		return asteroid.getPositionX();
	}

	@Override
	public double getAsteroidY(Asteroid asteroid) {
		return asteroid.getPositionY();
	}

	@Override
	public double getAsteroidXVelocity(Asteroid asteroid) {
		return asteroid.getVelocityX();
	}

	@Override
	public double getAsteroidYVelocity(Asteroid asteroid) {
		return asteroid.getVelocityY();
	}

	@Override
	public double getAsteroidRadius(Asteroid asteroid) {
		return asteroid.getRadius();
	}

	@Override
	public double getAsteroidMass(Asteroid asteroid) {
		return asteroid.getMass();
	}

	@Override
	public World getAsteroidWorld(Asteroid asteroid) {
		return asteroid.getWorld();
	}

	@Override
	public boolean isBullets(Object o) {
		return o instanceof asteroids.model.Bullet;
	}

	@Override
	public double getBulletX(Bullet bullet) {
		return bullet.getPositionX();
	}

	@Override
	public double getBulletY(Bullet bullet) {
		return bullet.getPositionY();
	}

	@Override
	public double getBulletXVelocity(Bullet bullet) {
		return bullet.getVelocityX();
	}

	@Override
	public double getBulletYVelocity(Bullet bullet) {
		return bullet.getVelocityY();
	}

	@Override
	public double getBulletRadius(Bullet bullet) {
		return bullet.getRadius();
	}

	@Override
	public double getBulletMass(Bullet bullet) {
		return bullet.getMass();
	}

	@Override
	public World getBulletWorld(Bullet bullet) {
		return bullet.getWorld();
	}

	@Override
	public Ship getBulletSource(Bullet bullet) {
		return bullet.getSource();
	}

	@Override
	public boolean isTypeCheckingSupported() {
		return true;
	}

	@Override
	public asteroids.IFacade.ParseOutcome<Program> loadProgramFromStream(InputStream stream) throws IOException {

		String result = getStringFromInputStream(stream);
		return parseProgram(result);
	}

	@Override
	public asteroids.IFacade.ParseOutcome<Program> loadProgramFromUrl(URL url) throws IOException {
		InputStream stream = url.openStream();
		return this.loadProgramFromStream(stream);
	}

	@SuppressWarnings( { "rawtypes", "unchecked" })
	@Override
	public asteroids.IFacade.ParseOutcome<Program> parseProgram(String text) {
		ProgramFactory factory = new ProgramFactory();
		ProgramParser<Expression, Statement, Type> parser = new ProgramParser<Expression, Statement, Type>(
				factory);
			parser.parse(text);
			List<String> errors = parser.getErrors();
			if (!errors.isEmpty()) {
				return ParseOutcome.failure(errors.get(0));
			} else {
				Program p = new Program(parser.getGlobals(), parser.getStatement());
				return ParseOutcome.success(p);
			}
	}

	@Override
	public void setShipProgram(Ship ship, Program program) {
		ship.setProgram(program);

	}

	@Override
	public asteroids.IFacade.TypeCheckOutcome typeCheckProgram(Program program) {
		
		String errorMessage = TypeChecker.typeCheck(program);		
		if(errorMessage == null){
			return TypeCheckOutcome.success();
		}
		else{
			return TypeCheckOutcome.failure(errorMessage);
		}
		
	}

	/**
	 * We found this on the interwebz.
	 * 
	 * @param is
	 * @return
	 */
	private static String getStringFromInputStream(InputStream is) {

		BufferedReader br = null;
		StringBuilder sb = new StringBuilder();

		String line;
		try {

			br = new BufferedReader(new InputStreamReader(is));
			while ((line = br.readLine()) != null) {
				sb.append(line);
			}

		}
		catch (IOException e) {
			e.printStackTrace();
		}
		finally {
			if (br != null) {
				try {
					br.close();
				}
				catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		return sb.toString();

	}

}
